package cn.uncode.rpc.config;


import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.commons.lang3.StringUtils;

import cn.uncode.rpc.common.CommonConstant;
import cn.uncode.rpc.common.log.Logger;
import cn.uncode.rpc.common.log.LoggerFactory;
import cn.uncode.rpc.core.ApplicationInfo;
import cn.uncode.rpc.core.Exporter;
import cn.uncode.rpc.core.Factory;
import cn.uncode.rpc.core.URL;
import cn.uncode.rpc.core.URLParam;
import cn.uncode.rpc.exception.ConfigException;
import cn.uncode.rpc.exception.FrameworkException;
import cn.uncode.rpc.registry.Registry;
import cn.uncode.rpc.spi.ExtensionLoader;
import cn.uncode.rpc.util.ConcurrentHashSet;
import cn.uncode.rpc.util.ConfigUtil;
import cn.uncode.rpc.util.NetUtil;
import cn.uncode.rpc.util.StringTools;



public class ProviderConfig<T> extends AbstractProviderConfig {
	
    private static final long serialVersionUID = -3342374271064293224L;
    
    private static final Logger LOGGER = LoggerFactory.getLogger(ProviderConfig.class);
    
    private static ConcurrentHashSet<String> existingServices = new ConcurrentHashSet<String>();
    // 具体到方法的配置
    protected List<MethodConfig> methods;

    // 接口实现类引用
    private T ref;

    // service 对应的exporters，用于管理service服务的生命周期
    private List<Exporter<T>> exporters = new CopyOnWriteArrayList<Exporter<T>>();
    private Class<T> interfaceClass;
    private BasicProviderConfig basicServiceConfig;
    private AtomicBoolean exported = new AtomicBoolean(false);
    // service的用于注册的url，用于管理service注册的生命周期，url为regitry url，内部嵌套service url。
    private ConcurrentHashSet<URL> registereUrls = new ConcurrentHashSet<URL>();

    public static ConcurrentHashSet<String> getExistingServices() {
        return existingServices;
    }

    public Class<?> getInterface() {
        return interfaceClass;
    }

    public void setInterface(Class<T> interfaceClass) {
        if (interfaceClass != null && !interfaceClass.isInterface()) {
            throw new IllegalStateException("The interface class " + interfaceClass + " is not a interface!");
        }
        this.interfaceClass = interfaceClass;
    }

    public List<MethodConfig> getMethods() {
        return methods;
    }

    public void setMethods(MethodConfig methods) {
        this.methods = Collections.singletonList(methods);
    }

    public void setMethods(List<MethodConfig> methods) {
        this.methods = methods;
    }

    public boolean hasMethods() {
        return this.methods != null && this.methods.size() > 0;
    }

    public T getRef() {
        return ref;
    }

    public void setRef(T ref) {
        this.ref = ref;
    }
    
    public List<Exporter<T>> getExporters() {
        return Collections.unmodifiableList(exporters);
    }

    protected boolean serviceExists(URL url) {
        return existingServices.contains(url.getIdentity());
    }

	public void setBasicServiceConfig(BasicProviderConfig basicServiceConfig) {
		this.basicServiceConfig = basicServiceConfig;
	}

	public BasicProviderConfig getBasicServiceConfig() {
		return basicServiceConfig;
	}
    
    public AtomicBoolean getExported() {
		return exported;
	}

	public synchronized void export(){
    	if(exported.get()){
    		LOGGER.warn(String.format("%s has already been expoted, so ignore the export request!", interfaceClass.getName()));
    		return;
    	}
    	
    	checkInterfaceAndMethods(interfaceClass, methods);
    	
    	List<URL> registryUrls = loadRegistryUrls();
    	if (registryUrls == null || registryUrls.size() == 0) {
            throw new IllegalStateException("Should set registry config for service:" + interfaceClass.getName());
        }
    	
    	Map<String, Integer> protocolPorts = getProtocolAndPort();
    	for (ProtocolConfig protocolConfig : protocols) {
            Integer port = protocolPorts.get(protocolConfig.getId());
            if (port == null) {
                throw new ConfigException(String.format("Unknow port in service:%s, protocol:%s", interfaceClass.getName(),
                        protocolConfig.getId()));
            }
            doExport(protocolConfig, port, registryUrls);
        }
    	afterExport();

    }
    
	private void afterExport() {
        exported.set(true);
        for (Exporter<T> ep : exporters) {
            existingServices.add(ep.getInvoker().getUrl().getIdentity());
        }
    }
    
    private void doExport(ProtocolConfig protocolConfig, int port, List<URL> registryURLs) {
    	String protocolName = protocolConfig.getName();
    	if(StringUtils.isBlank(protocolName)){
    		protocolName = URLParam.PROTOCOL.getValue();
    	}
    	
    	String hostAddress = host;
    	if(StringUtils.isBlank(hostAddress) && basicServiceConfig != null){
    		hostAddress = basicServiceConfig.getHost();
    	}
    	if(NetUtil.isInvalidLocalHost(hostAddress)){
    		hostAddress = this.getLocalHostAddress(registryURLs);
    	}
    	
    	Map<String, String> map = new HashMap<String, String>();

        map.put(URLParam.NODE_TYPE.getName(), CommonConstant.NODE_TYPE_SERVICE);
        map.put(URLParam.REFRESH_TIMESTAMP.getName(), String.valueOf(System.currentTimeMillis()));

        collectConfigParams(map, protocolConfig, basicServiceConfig, extConfig, this);
        collectMethodConfigParams(map, this.getMethods());

        URL serviceUrl = new URL(protocolName, hostAddress, port, interfaceClass.getName(), map);

        if (serviceExists(serviceUrl)) {
        	LOGGER.warn(String.format("%s configService is malformed, for same service (%s) already exists ", interfaceClass.getName(),
                    serviceUrl.getIdentity()));
            throw new FrameworkException(String.format("%s configService is malformed, for same service (%s) already exists ",
                    interfaceClass.getName(), serviceUrl.getIdentity()));
        }

        List<URL> urls = new ArrayList<URL>();

        // injvm 协议只支持注册到本地，其他协议可以注册到local、remote
        if (CommonConstant.PROTOCOL_INJVM.equals(protocolConfig.getName())) {
            URL localRegistryUrl = null;
            for (URL ru : registryURLs) {
                if (CommonConstant.REGISTRY_PROTOCOL_LOCAL.equals(ru.getProtocol())) {
                    localRegistryUrl = ru.createCopy();
                    break;
                }
            }
            if (localRegistryUrl == null) {
                localRegistryUrl =
                        new URL(CommonConstant.REGISTRY_PROTOCOL_LOCAL, hostAddress, CommonConstant.DEFAULT_INT_VALUE,
                                Registry.class.getName());
            }

            urls.add(localRegistryUrl);
        } else {
            for (URL ru : registryURLs) {
                urls.add(ru.createCopy());
            }
        }

        for (URL u : urls) {
        	//service url
        	//injvm://192.168.1.61:8001/cn.uncode.rpc.api.DemoService?application=myMotanDemo&module=uncode-rpc-demo&name=injvm&shareChannel=true&refreshTimestamp=1486548367369
            //&id=cn.uncode.rpc.spring.ProviderConfigBean&nodeType=service&export=demoMotan:8001&accessLog=false&group=motan-demo-rpc&
            u.addParameter(URLParam.EMBED.getName(), StringTools.urlEncode(serviceUrl.toFullStr()));
            registereUrls.add(u.createCopy());
        }
        
        Factory factory = ExtensionLoader.getExtensionLoader(Factory.class).getExtension(CommonConstant.DEFAULT_VALUE);
        exporters.add(factory.getExport(interfaceClass, ref, urls));

        initLocalAppInfo(serviceUrl);
    }
    
    
    protected void initLocalAppInfo(URL localUrl) {
        ApplicationInfo.addService(localUrl);
    }
    
    
    public synchronized void unexport() {
        if (!exported.get()) {
            return;
        }
        try {
            ConfigUtil.unexport(exporters, registereUrls);
        } finally {
            afterUnexport();
        }
    }
    
    private void afterUnexport() {
        exported.set(false);
        for (Exporter<T> ep : exporters) {
            existingServices.remove(ep.getInvoker().getUrl().getIdentity());
            exporters.remove(ep);
        }
        exporters.clear();
        registereUrls.clear();
    }
    
    
    public Map<String, Integer> getProtocolAndPort() {
        if (StringUtils.isBlank(export)) {
            throw new ConfigException("export should not empty in service config:" + interfaceClass.getName());
        }
        return ConfigUtil.parseExport(this.export);
    }
    
    

    

}
